use actix_web::web::{BufMut, BytesMut};
use actix_web::{FromRequest, HttpRequest};

use futures::StreamExt;
use serde::de::DeserializeOwned;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{ready, Poll};
use std::{
    future::Future,
    ops::{Deref, DerefMut},
};

use crate::error::{new_err, new_err_static, AppErr};

pub const CBOR_MAX_SIZE: usize = 20 * 1024 * 1024;

pub struct Cbor<T>(T);

impl<T> Deref for Cbor<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> DerefMut for Cbor<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

// async fn take_resp<T: DeserializeOwned>(
//     mut payload: actix_web::dev::Payload,
// ) -> Result<Cbor<T>, AppErr> {
//     let mut buf = BytesMut::new();
//     while let Some(item) = payload.next().await {
//         let mut item = item.map_err(|_| new_err_static("resp item fail"))?;
//         if (buf.len() + item.len()) > CBOR_MAX_SIZE {
//             return Err(new_err_static("resp too large"));
//         }
//         buf.put_slice(&mut item);
//     }

//     let resp = serde_cbor::from_slice(&buf)?;
//     Ok(Cbor(resp))
// }

// type Fut<T> = Pin<Box<dyn Future<Output = Result<Cbor<T>, AppErr>>>>;

impl<T: DeserializeOwned + Unpin> FromRequest for Cbor<T> {
    type Error = AppErr;
    type Future = CborFut<T>;

    fn from_request(_req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
        //Box::pin(take_resp(payload.take()))
        CborFut {
            payload: payload.take(),
            buf: BytesMut::new(),
            _n: PhantomData,
        }
    }
}

pub struct CborFut<T> {
    payload: actix_web::dev::Payload,
    buf: BytesMut,
    _n: PhantomData<T>,
}

impl<T: DeserializeOwned + Unpin> Future for CborFut<T> {
    type Output = Result<Cbor<T>, AppErr>;

    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
        let this = self.get_mut();
        let item = ready!(this.payload.poll_next_unpin(cx));

        if let Some(item) = item {
            match item {
                Ok(item) => {
                    if this.buf.len() + item.len() > CBOR_MAX_SIZE {
                        Poll::Ready(Err(new_err_static("body large")))
                    } else {
                        this.buf.put_slice(&item);
                        Poll::Pending
                    }
                }
                Err(e) => Poll::Ready(Err(new_err(e.to_string()))),
            }
        } else {
            match serde_cbor::from_slice::<T>(&this.buf) {
                Ok(v) => Poll::Ready(Ok(Cbor(v))),

                Err(e) => Poll::Ready(Err(AppErr::Cbor(e))),
            }
        }
    }
}
